package scala.subscala

import scala.util.parsing.ast._
import scala.util._
  
trait Syntax requires (Syntax with SyntaxIsMappable) extends Binders {
  trait Term extends Element {
    // @return false|true
    def isPath: Boolean = false
  }
  
  case class New(tp: Type, args: Option[List[Term]]) extends Term {
    override def toString = "new "+tp
  }
  
  case class Let(tp: Type, value: Term, scope: UnderBinder[Name, Term]) extends Term {
    override def toString = "val "+scope.scope+": "+tp+" = "+value+" in {"+scope.elementToString+"}"
  }
  
  case class TMUnit extends Term {
    override def toString = "unit"
  }
  
  case class MethodCall(target: Term, name: String, actuals: List[Term]) extends Term {
    override def toString = target+"."+name+actuals.mkString("(",", ",")")
  }
  
  case class FieldSel(target: Term, name: String) extends Term {
    override def isPath = target.isPath
    override def toString = target.toString+"."+name
  }

  case class Name(n: String) extends NameElement with Term { 
    override def toString = n
    def name=n 
    override def isPath=true
  }

  // TODO: case classes can't have companion objects?
  object Path_ {
    // @pre tm.isPath
    def fromTerm(tm: Term): Path = if(tm.isPath) {
      tm match {
        case FieldSel(target,name) => fromTerm(target) ++ Path(List(Name(name)))
        case name@Name(n) => Path(List(name))
        case p: Path => p
      }
    } else {
      error("fromTerm on non-path: "+tm)
    }
    
    def fromName(n: NameElement): Path = new Path(List(n))
  }
  
  case class Path(comps: List[NameElement]) extends Term with NameElement {
    assert(comps.length > 0)
    def name = comps.mkString("",".","")
    override def isPath = true
    override def toString = name
    def split: (Path, String) = (new Path(comps.take(comps.length-1)), comps(comps.length-1).name)
    def ++(other: Path): Path = new Path(List.concat(comps, other.comps))
  }
  
  
  trait Type extends Element
  case class Template(argsFirstParent: Option[List[Term]], parents: List[Type], selfTp: Type, members: UnderBinder[Name, List[Member]]) extends Type {
    override def toString = parents.mkString("(",", ",") ")+"{ "+members.scope.bindersToString.head+
                        unsequence(members).map(.elementToString).toList.mkString(" =>\n","\n","\n}")
  }
  
  case class TPUnit extends Type {
    override def toString = "Unit"
  } 

  case class Self extends Type {
    override def toString = "Self"
  } 
  
  case class SingletonType(path: Path) extends Type {
    override def toString = path+".type"
  }
  
  case class TypeSel(target: Path, name: String) extends Type {
    override def toString = target.toString+"."+name
  }
  
  
  // TODO: naming...
  trait Member extends Element {
    val name: String
    def matches(that: Member): Boolean = name == that.name
    def isConcrete: Boolean
    def isValue: Boolean
  }
  
  trait ValueMember[+t] extends Member {
    val tp: Type
    val value: Option[t]
    def getTermValue: Term
    def isConcrete = !value.isEmpty
    def isValue = true
  }
  
  trait TypeMember[+t <: Type] extends Member {
    val value: Option[t]
    def getTypeValue: Type=value.get
    def isConcrete = !value.isEmpty
    def isValue = false    
  } 
  
  // TODO: doesn't seem to work
 /* object TypeMemberUnapp {
    def unapply[t <: Type](tpMem: TypeMember[t]): Option[t] = tpMem.value
  }*/
  
  case class MField(name: String, tp: Type, value: Option[Term]) extends ValueMember[Term] {
    def getTermValue: Term = value.get
    override def toString = "val "+name+": "+tp+" = "+(value match {case Some(v) => v case _ => ""})
  }
  
  case class Param(higherOrderParams_Bound: UnderBinder[Name, Option[List[Param]] ~ Option[Type]])
 
  case class MMethod(name: String, sig: UnderBinder[Name, Option[List[Param]] ~ /*formals: */Option[List[Type]] ~ /*tp: */Type ~ /*value: */Option[Term]]) extends ValueMember[UnderBinder[Name, Term]] {
    def getTermValue: Term = value.get.extract
    
    protected def argTpPairs = (value match {
       case Some(ub) => ub.scope.bindersToString
       case _ => List.make(formals.length, "?")}).zip(formals)
    
    override def toString = "def "+name+
        (for(val Pair(arg, argtp) <- argTpPairs) yield arg+": "+argtp).toList.mkString("(",", ",")")+
        ": "+tp+" = "+(value match {case Some(v) => v.elementToString case _ => ""})
  }
  
  case class MType(name: String, value: Option[Type]) extends TypeMember[Type] {    
    override def toString = "type "+name+" = "+(value match {case Some(v) => v case _ => ""})
  }
  
  case class MTrait(name: String, cls: TypeSig) extends TypeMember[TypeSig] {
    val value = Some(cls)
    override def toString = "trait "+name+" " + value 
  }
}

// boilerplate that should also be scrapped:
trait SyntaxIsMappable extends Syntax {

  
  implicit def NameIsMappable(self: Name) = UserNameElementIsMappable(self)
  def UserNameElementIsMappable[t <: NameElement](self: t): Mappable[t] = new Mappable[t]{ // requires: f <: {tcase String => String} (provides: Name => Name)
    def gmap(f: Mapper): t = self match {
      case Name(name)  => Name(f(name)).asInstanceOf[t] // TODO: Name may have a subclass, so simply mapping Name to Name may actually map SpecialisedName to Name
    }
  }

  implicit def PathIsMappable(self: Path): Mappable[Path] = new Mappable[Path] { // requires: {tcase List[NameElement] => List[NameElement]}
    def gmap(f: Mapper) = Path(f(self.comps)) 
  }
  
  implicit def TermIsMappable(self: Term): Mappable[Term] = new Mappable[Term] { // requires: {tcase Type => Type tcase Term => Term  .... }
    def gmap(f: Mapper) = self match {
      case New(tp) => New(f(tp))
      case Let(tp, value, scope) => Let(f(tp), f(value), f(scope))
      case TMUnit() => TMUnit
      case MethodCall(target, name, actuals) => MethodCall(f(target), f(name), f(actuals))
      case FieldSel(target, name)  => FieldSel(f(target), f(name))
      case Path(comps) => Path(f(comps)) 
    }
  }  
  
  implicit def TemplateIsMappable(self: Template): Mappable[Template] = new Mappable[Template] {
    def gmap(f: Mapper) = self match {
      case Template(argsFirstParent, parents, selfTp, members) => Template(argsFirstParent.gmap(f), parents.gmap(f), f(selfTp), members.gmap(f))
    }
  }
  
  implicit def TypeIsMappable(self: Type): Mappable[Type] = new Mappable[Type] {
    def gmap(f: Mapper) = self match {
      case TypeSig(parents, members) => TypeSig(f(parents), f(members))
      case TPUnit() => TPUnit
      case SingletonType(path) => SingletonType(f(path))
      case TypeSel(target, name) => TypeSel(f(target), name) 
    }
  }
    
  implicit def MemberIsMappable(self: Member): Mappable[Member] = new Mappable[Member] {
    def gmap(f: Mapper) = self match {
      case MField(name, tp, value)  => MField(f(name), f(tp), f(value))
      case MMethod(name, formals, tp, value) => MMethod(f(name), f(formals), f(tp), f(value))
      case MType(name, value) => MType(f(name), f(value))
      case MTrait(name, cls) => MTrait(f(name), f(cls)) // TODO: cast
    }
  }
  
  implicit def ParamIsMappable(self: Param): Mappable[Param] = new Mappable[Param] {
    def gmap(f: Mapper) = self match {
      case Param(higherOrderParams_Bound) => error("foo") //TypeSig(f(parents), f(members))
    }
  }
}